/*
**  Copyright (c) 2014 Michael Liu(michael.liu.point@gmail.com).
**
**  Project: Gateway Unified Management Platform
**  File:    cmapi.c
**  Author:  Michael
**  Date:    03/20/2014
**
**  Purpose:
**     Common Management API implement.
*/

/* Include files. */

#include "cmapi.h"

/* Macro constant definitions. */

#define SEND_MSG_TIMEOUT_SECOND		5
#define SEND_MSG_TIMEOUT_USECOND	0

#define RECV_MSG_TIMEOUT_SECOND		60
#define RECV_MSG_TIMEOUT_USECOND	0

/* Type definitions. */

/* Local function declarations. */

static e_ret init_session(tcm_session * session, const char *name);
static void destroy_session(tcm_session * session);
static e_ret local_cmapi_connect(int *cmsvc_socket);
static e_ret local_cmapi_send_msg(tcm_session * session, struct timeval *timeout);
static e_ret local_cmapi_recv_msg(tcm_session * session, struct timeval *timeout);

/* Macro API definitions. */

/* Global variable declarations. */

e_ret init_session(tcm_session * session, const char *name)
{

	cmapi_trace_enter();

	str_safe_cpy(session->name, name, CM_SESSION_NAME_SIZE - 1);
	session->id = CM_SESSION_ID_NONE;
	session->conn_fd = CM_INVALID_FD;
	session->transid = 0;
	session->msg_len = 0;
	session->buf_len = CM_MSG_BUFF_SIZE;
	session->msg_buf = malloc(session->buf_len);
	if (session->msg_buf == NULL) {
		cmapi_error("malloc(%d) failed, err: %d,[%s]\n", session->buf_len, errno, strerror(errno));
		cmapi_trace_exit();
		return (eRET_NO_RESOURCE);
	}

	cmapi_trace_exit();
	return (eRET_SUCCESS);

}

void destroy_session(tcm_session * session)
{
	cmapi_trace_enter();
	if (session != NULL) {
		if (session->msg_buf != NULL) {
			free(session->msg_buf);
			session->msg_buf = NULL;
			session->buf_len = 0;
		}
		if (session->conn_fd != CM_INVALID_FD) {
			close(session->conn_fd);
			session->conn_fd = CM_INVALID_FD;
		}
		session->id = CM_SESSION_ID_NONE;
		session->transid = 0;
	}
	cmapi_trace_exit();
}

e_ret local_cmapi_connect(int *cmsvc_socket)
{

	int rc;
	int fd;
	long fd_flags;
	socklen_t len;
	struct sockaddr_un addr;

	cmapi_trace_enter();

	fd = socket(AF_UNIX, SOCK_STREAM, 0);
	if (fd < 0) {
		cmapi_error("socket() failed, err: %d,[%s]\n", errno, strerror(errno));
		return (eRET_FAILURE);
	}

	memset(&addr, 0, sizeof(addr));
	addr.sun_family = AF_UNIX;
	strcpy(addr.sun_path, CONFIG_CM_SOCKET);
	len = sizeof(addr);

	fd_flags = fcntl(fd, F_GETFL, 0);
	fcntl(fd, F_SETFL, fd_flags | O_NONBLOCK);

	rc = connect(fd, (struct sockaddr *)&addr, len);
	if (rc < 0) {
		if (errno != EINPROGRESS) {
			cmapi_error("connect() failed, err: %d,[%s]\n", errno, strerror(errno));
			close(fd);
			return (eRET_FAILURE);
		}
	}

	*cmsvc_socket = fd;

	cmapi_trace_exit();
	return (eRET_SUCCESS);
}

e_ret local_cmapi_send_msg(tcm_session * session, struct timeval * timeout)
{

	e_ret ret;
	int rc;
	int cmsvc_socket;
	int msg_len;
	int send_len;
	int select_counter;
	unsigned char *tx_buff;
	tcm_msg *msg;
	fd_set send_set;

	cmapi_trace_enter();

	ret = eRET_SUCCESS;
	cmsvc_socket = session->conn_fd;
	msg = session->msg_buf;
	tx_buff = (unsigned char *)msg;
	send_len = 0;
	msg_len = session->msg_len;

	do {
		FD_ZERO(&send_set);
		FD_SET(cmsvc_socket, &send_set);

		select_counter = select(cmsvc_socket + 1, NULL, &send_set, NULL, timeout);
		if (select_counter == 0) {
			if (timeout != NULL) {
				/* Time out... */
				cmapi_debug("select() timeout for send() err: %d,[%s]\n", errno, strerror(errno));
				ret = eRET_TIMEOUT;
				goto done;
			} else {
				continue;
			}
		} else if (select_counter < 0) {
			/* select be interrupt? */
			if (errno != EINTR) {
				cmapi_debug("select() return %d, err: %d,[%s]\n", select_counter, errno,
					    strerror(errno));
				ret = eRET_FAILURE;
				goto done;
			} else {
				continue;
			}
		}

		if (FD_ISSET(cmsvc_socket, &send_set)) {
			rc = send(cmsvc_socket, (const void *)&tx_buff[send_len], msg_len - send_len, 0);
			if (rc <= 0) {
				if ((errno == EAGAIN) || (errno == EINTR)
				    || (errno == EWOULDBLOCK)) {
					continue;
				} else {
					cmapi_error("send() return %d, err: %d,[%s]\n", rc, errno, strerror(errno));
					ret = eRET_FAILURE;
					goto done;
				}
			} else {
				send_len += rc;
			}
		}
	} while (send_len < msg_len);

done:
	if (ret == eRET_SUCCESS) {
		session->transid++;
	}
	cmapi_trace_exit();
	return (ret);
}

e_ret local_cmapi_recv_msg(tcm_session * session, struct timeval * timeout)
{

	e_ret ret;
	int rc;
	int buf_len;
	int msg_len;
	int recv_len;
	int cmsvc_socket;
	int select_counter;
	e_bool got_msg_hdr;
	tcm_msg *msg;
	unsigned char *rx_buff;
	fd_set recv_set;

	cmapi_trace_enter();

	ret = eRET_SUCCESS;
	msg = session->msg_buf;
	cmsvc_socket = session->conn_fd;
	recv_len = 0;
	session->msg_len = 0;
	msg_len = buf_len = session->buf_len;
	rx_buff = (unsigned char *)msg;
	got_msg_hdr = eBOOL_FALSE;

	do {
		FD_ZERO(&recv_set);
		FD_SET(cmsvc_socket, &recv_set);

		select_counter = select(cmsvc_socket + 1, &recv_set, NULL, NULL, timeout);
		if (select_counter == 0) {
			if (timeout != NULL) {
				/* Time out... */
				cmapi_debug("select() timeout for recv() err: %d,[%s]\n", errno, strerror(errno));
				ret = eRET_TIMEOUT;
				goto done;
			} else {
				continue;
			}
		} else if (select_counter < 0) {
			/* select be interrupt? */
			if (errno != EINTR) {
				cmapi_debug("select() return %d err: %d,[%s]\n", select_counter, errno,
					    strerror(errno));
				ret = eRET_FAILURE;
				goto done;
			} else {
				continue;
			}
		}

		if (FD_ISSET(cmsvc_socket, &recv_set)) {
			rc = recv(cmsvc_socket, (void *)&rx_buff[recv_len], buf_len - recv_len, 0);
			if (rc > 0) {
				recv_len += rc;
				if (!got_msg_hdr) {
					if (recv_len >= sizeof(tcm_msg)) {
						got_msg_hdr = eBOOL_TRUE;
						msg_len = sizeof(tcm_msg) + msg->header.opt_body_len;
						if (buf_len < msg_len) {
							buf_len = msg_len;
							rx_buff = malloc(buf_len);
							if (rx_buff == NULL) {
								cmapi_error("malloc(%d) failed, err: %d,[%s]\n",
									    buf_len, errno, strerror(errno));
								ret = eRET_NO_RESOURCE;
								goto done;
							} else {
								memcpy(rx_buff, msg, recv_len);
								free(session->msg_buf);
								session->msg_buf = (tcm_msg *) rx_buff;
								session->buf_len = buf_len;
							}
						}
					}
				}
				session->msg_len = recv_len;
			} else if (rc < 0) {
				if ((errno == EAGAIN) || (errno == EINTR)
				    || (errno == EWOULDBLOCK)) {
					continue;
				} else {
					cmapi_error("recv() return %d err: %d,[%s]\n", rc, errno, strerror(errno));
					ret = eRET_FAILURE;
					goto done;
				}
			} else {
				cmapi_error("recv() peer shutdown!\n");
				ret = eRET_FAILURE;
				break;
			}
		}
	} while (recv_len < msg_len);

done:
	if (ret == eRET_SUCCESS) {
		session->transid = session->msg_buf->header.transid++;
	}
	cmapi_trace_exit();
	return (ret);
}

e_ret cmapi_create_session(tcm_session * session, const char *name)
{

	e_ret ret;
	int req_size;
	int cmsvc_socket;
	tcm_msg *msg;
	tcm_session_body *body;
	struct timeval timeout;

	cmapi_trace_enter();

	if ((session == NULL) || (name == NULL)) {
		cmapi_trace_exit();
		return (eRET_INVALID_ARG);
	}

	ret = init_session(session, name);
	if (ret != eRET_SUCCESS) {
		cmapi_trace_exit();
		return (ret);
	}

	req_size = sizeof(tcm_session_body);
	if ((session->msg_buf == NULL) || (session->buf_len < req_size + sizeof(tcm_msg))) {
		msg = malloc(sizeof(tcm_msg) + req_size);
		if (msg == NULL) {
			cmapi_error("malloc(%d) failed, err: %d,[%s]\n",
				    (sizeof(tcm_msg) + req_size), errno, strerror(errno));
			cmapi_trace_exit();
			return (eRET_NO_RESOURCE);
		}
		session->msg_buf = msg;
		session->buf_len = sizeof(tcm_msg) + req_size;
	} else {
		msg = session->msg_buf;
	}

	memset(session->msg_buf, 0, req_size + sizeof(tcm_msg));
	msg = session->msg_buf;
	msg->header.opt = eCM_OPERATION_CREATE_SESSION;
	msg->header.dir = eCM_DIR_CALL;
	msg->header.sessionid = session->id;
	msg->header.transid = session->transid;
	msg->header.opt_counter = 1;
	msg->header.opt_body_len = sizeof(tcm_session_body);

	body = (tcm_session_body *) msg->opt_body;
	str_safe_cpy(body->from, session->name, CM_SESSION_NAME_SIZE - 1);
	session->msg_len = sizeof(tcm_msg) + msg->header.opt_body_len;

	ret = local_cmapi_connect(&cmsvc_socket);
	if (ret != eRET_SUCCESS) {
		cmapi_error("local_cmapi_connect() failed.\n");
		goto err;
	}

	session->conn_fd = cmsvc_socket;

	timeout.tv_sec = SEND_MSG_TIMEOUT_SECOND;
	timeout.tv_usec = SEND_MSG_TIMEOUT_USECOND;
	ret = local_cmapi_send_msg(session, &timeout);
	if (ret != eRET_SUCCESS) {
		cmapi_error("local_cmapi_send_msg() failed.\n");
		goto err;
	}

	timeout.tv_sec = RECV_MSG_TIMEOUT_SECOND;
	timeout.tv_usec = RECV_MSG_TIMEOUT_USECOND;
	ret = local_cmapi_recv_msg(session, &timeout);
	if (ret != eRET_SUCCESS) {
		cmapi_error("local_cmapi_recv_msg() failed.\n");
		goto err;
	}

	msg = session->msg_buf;
	if (msg->header.ret != eRET_SUCCESS) {
		cmapi_error("received msg indicate operation failed, ret: %d.\n", msg->header.ret);
		goto err;
	} else {
		session->id = msg->header.sessionid;
		session->conn_fd = cmsvc_socket;
	}

	cmapi_trace_exit();
	return (eRET_SUCCESS);

err:
	destroy_session(session);
	return (ret);

}

e_ret cmapi_destroy_session(tcm_session * session)
{

	e_ret ret;
	tcm_msg *msg;
	int req_size;
	tcm_session_body *body;
	struct timeval timeout;

	cmapi_trace_enter();

	ASSERT(session != NULL);

	if ((session->conn_fd == CM_INVALID_FD) || (session->id == CM_SESSION_ID_NONE)) {
		cmapi_debug("session[%d] conn_fd: [%d]\n", session->id, session->conn_fd);
		cmapi_trace_exit();
		return (eRET_INVALID_ARG);
	}

	req_size = sizeof(tcm_session_body);
	if ((session->msg_buf == NULL) || (session->buf_len < req_size + sizeof(tcm_msg))) {
		msg = malloc(sizeof(tcm_msg) + req_size);
		if (msg == NULL) {
			cmapi_error("malloc(%d) failed, err: %d,[%s]\n",
				    (sizeof(tcm_msg) + req_size), errno, strerror(errno));
			cmapi_trace_exit();
			return (eRET_NO_RESOURCE);
		}
		session->msg_buf = msg;
		session->buf_len = sizeof(tcm_msg) + req_size;
	} else {
		msg = session->msg_buf;
	}

	memset(session->msg_buf, 0, req_size + sizeof(tcm_msg));
	msg = session->msg_buf;
	msg->header.opt = eCM_OPERATION_DESTORY_SESSION;
	msg->header.dir = eCM_DIR_CALL;
	msg->header.sessionid = session->id;
	msg->header.transid = session->transid;
	msg->header.opt_counter = 1;
	msg->header.opt_body_len = sizeof(tcm_session_body);

	body = (tcm_session_body *) msg->opt_body;
	str_safe_cpy(body->from, session->name, CM_SESSION_NAME_SIZE - 1);
	session->msg_len = sizeof(tcm_msg) + msg->header.opt_body_len;

	timeout.tv_sec = SEND_MSG_TIMEOUT_SECOND;
	timeout.tv_usec = SEND_MSG_TIMEOUT_USECOND;
	ret = local_cmapi_send_msg(session, &timeout);
	if (ret != eRET_SUCCESS) {
		cmapi_error("local_cmapi_send_msg() failed.\n");
		goto out;
	}

	timeout.tv_sec = RECV_MSG_TIMEOUT_SECOND;
	timeout.tv_usec = RECV_MSG_TIMEOUT_USECOND;
	ret = local_cmapi_recv_msg(session, &timeout);
	if (ret != eRET_SUCCESS) {
		cmapi_error("local_cmapi_recv_msg() failed.\n");
		goto out;
	}

	msg = session->msg_buf;
	if (msg->header.ret != eRET_SUCCESS) {
		cmapi_error("received msg indicate operation failed, ret: %d.\n", msg->header.ret);
		ret = msg->header.ret;
	}

out:
	destroy_session(session);
	return (ret);
}

e_ret cmapi_require(tcm_session * session, int counter, void *req_body, int req_size, void **rsp_body, int *rsp_size)
{

	e_ret ret;
	tcm_msg *msg;
	void *new_rsp;
	struct timeval timeout;

	cmapi_trace_enter();

	ASSERT(session != NULL);

	if ((session->conn_fd == CM_INVALID_FD) || (session->id == CM_SESSION_ID_NONE)) {
		cmapi_debug("session[%d] conn_fd: [%d]\n", session->id, session->conn_fd);
		cmapi_trace_exit();
		return (eRET_INVALID_ARG);
	}

	if ((session->msg_buf == NULL) || (session->buf_len < req_size + sizeof(tcm_msg))) {
		msg = malloc(sizeof(tcm_msg) + req_size);
		if (msg == NULL) {
			cmapi_error("malloc(%d) failed, err: %d,[%s]\n",
				    (sizeof(tcm_msg) + req_size), errno, strerror(errno));
			cmapi_trace_exit();
			return (eRET_NO_RESOURCE);
		}
		session->msg_buf = msg;
		session->buf_len = sizeof(tcm_msg) + req_size;
	} else {
		msg = session->msg_buf;
	}

	memset(session->msg_buf, 0, req_size + sizeof(tcm_msg));
	msg->header.opt = eCM_OPERATION_REQUEST;
	msg->header.dir = eCM_DIR_CALL;
	msg->header.sessionid = session->id;
	msg->header.transid = session->transid;
	msg->header.opt_counter = counter;
	msg->header.opt_body_len = req_size;

	memcpy((void *)msg->opt_body, req_body, req_size);
	session->msg_len = sizeof(tcm_msg) + msg->header.opt_body_len;

	timeout.tv_sec = SEND_MSG_TIMEOUT_SECOND;
	timeout.tv_usec = SEND_MSG_TIMEOUT_USECOND;
	ret = local_cmapi_send_msg(session, &timeout);
	if (ret != eRET_SUCCESS) {
		cmapi_error("local_cmapi_send_msg() failed.\n");
		destroy_session(session);
		cmapi_trace_exit();
		return (ret);
	}

	timeout.tv_sec = RECV_MSG_TIMEOUT_SECOND;
	timeout.tv_usec = RECV_MSG_TIMEOUT_USECOND;
	ret = local_cmapi_recv_msg(session, &timeout);
	if (ret != eRET_SUCCESS) {
		cmapi_error("local_cmapi_recv_msg() failed.\n");
		destroy_session(session);
		cmapi_trace_exit();
		return (ret);
	}
	msg = session->msg_buf;

	if (msg->header.opt_body_len != 0) {
		if ((*rsp_body == NULL) || (*rsp_size < msg->header.opt_body_len)) {
			new_rsp = malloc(msg->header.opt_body_len);
			if (new_rsp == NULL) {
				cmapi_error("malloc(%d) failed, err: %d,[%s]\n",
					    (msg->header.opt_body_len), errno, strerror(errno));
				cmapi_trace_exit();
				return (eRET_NO_RESOURCE);
			}
			*rsp_body = new_rsp;
		} else {
			new_rsp = *rsp_body;
		}
		memcpy(new_rsp, msg->opt_body, msg->header.opt_body_len);
	}
	*rsp_size = msg->header.opt_body_len;

	if (msg->header.ret != eRET_SUCCESS) {
		cmapi_error("received msg indicate operation failed, ret: %d.\n", msg->header.ret);
		ret = msg->header.ret;
	}

	cmapi_trace_exit();
	return (ret);
}
